﻿namespace Monadic
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    using System.Collections;
    using System.Reflection;

    public static class Functive
    {
        public static Func<T, TResult> Cast<TCast, T, TResult>(this Func<T, TResult> func)
        {
            return func;
        }
        
        public static Func<M, MResult> Bind<M, MResult, T, TResult>(this Func<T, TResult> func)
        {
            return m =>
                {
                    var field = m.GetType().GetField("value");
                    var value = (T)field.GetValue(m);
                    var result = (TResult)func(value);
                    var constructor = m.GetType().GetConstructor(new[] { typeof(TResult) });
                    var mResult = (MResult)constructor.Invoke(new object[] { result });
                    return mResult;
                };
        }
        
        public static Func<T2, T1, TResult> Flip<T1, T2, TResult>(this Func<T1, T2, TResult> func)
        {
            return (x, y) => func(y, x);
        }

        public static IEnumerable<T> Iterate<T>(this Func<T, T> func, T value)
        {
            while (true)
            {
                yield return value;
                value = func(value);
            }
        }

        #region Select

        public static Func<TResult2> Select<TResult, TResult2>(this Func<TResult> func, Func<TResult, TResult2> selector)
        {
            return () => selector(func());
        }
        
        public static Func<T, TResult2> Select<T, TResult, TResult2>(this Func<T, TResult> func, Func<TResult, TResult2> selector)
        {
            return a => selector(func(a));
        }

        public static Func<T1, T2, TResult2> Select<T1, T2, TResult, TResult2>(this Func<T1, T2, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b) => selector(func(a, b));
        }

        public static Func<T1, T2, T3, TResult2> Select<T1, T2, T3, TResult, TResult2>(this Func<T1, T2, T3, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c) => selector(func(a, b, c));
        }

        public static Func<T1, T2, T3, T4, TResult2> Select<T1, T2, T3, T4, TResult, TResult2>(this Func<T1, T2, T3, T4, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c, d) => selector(func(a, b, c, d));
        }

        public static Func<T1, T2, T3, T4, T5, TResult2> Select<T1, T2, T3, T4, T5, TResult, TResult2>(this Func<T1, T2, T3, T4, T5, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c, d, e) => selector(func(a, b, c, d, e));
        }

        public static Func<T1, T2, T3, T4, T5, T6, TResult2> Select<T1, T2, T3, T4, T5, T6, TResult, TResult2>(this Func<T1, T2, T3, T4, T5, T6, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c, d, e, f) => selector(func(a, b, c, d, e, f));
        }

        public static Func<T1, T2, T3, T4, T5, T6, T7, TResult2> Select<T1, T2, T3, T4, T5, T6, T7, TResult, TResult2>(this Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c, d, e, f, g) => selector(func(a, b, c, d, e, f, g));
        }

        public static Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult2> Select<T1, T2, T3, T4, T5, T6, T7, T8, TResult, TResult2>(this Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> func, Func<TResult, TResult2> selector)
        {
            return (a, b, c, d, e, f, g, h) => selector(func(a, b, c, d, e, f, g, h));
        }

        #endregion

        #region SelectMany

        public static Func<T, T2, TResult3> SelectMany<T, T2, TResult, TResult2, TResult3>(this Func<T, TResult> leftFunc, Func<T2, TResult2> rightFunc, Func<TResult, TResult2, TResult3> selector)
        {
            return (x, y) => selector(leftFunc(x), rightFunc(y));
        }

        public static Func<T, T2, TResult3> SelectMany<T, T2, TResult, TResult2, TResult3>(this Func<T, TResult> leftFunc, Func<T, Func<T2, TResult2>> rightFunc, Func<TResult, TResult2, TResult3> selector)
        {
            return (x, y) => selector(leftFunc(x), rightFunc(x)(y));
        }

        #endregion

        #region Sum

        public static Func<Int32, Int32> Sum(this Func<Int32, Int32> func, int start = 0) 
        {
            return end =>
                {
                    Int32 result = default(Int32);

                    for (int k = start; k <= end; k++)
                    {
                        result += func(k);
                    }

                    return result;
                };
        }

        public static Func<Int32, Decimal> Sum(this Func<Int32, Decimal> func, int start = 0)
        {
            return end =>
            {
                Decimal result = default(Decimal);

                for (int k = start; k <= end; k++)
                {
                    result += func(k);
                }

                return result;
            };
        }

        /// <remarks>
        /// VB Query Expression (Aggregate Into Sum) does not work with optional parameters.
        /// </remarks>
        public static Func<Int32, Double> Sum(this Func<Int32, Double> func)
        {
            return Sum(func, 0);
        }

        public static Func<Int32, Double> Sum(this Func<Int32, Double> func, int start)
        {
            return end =>
            {
                Double result = default(Double);

                for (int k = start; k <= end; k++)
                {
                    result += func(k);
                }

                return result;
            };
        }

        //public static Func<T, TResult> Sum<T, TResult>(this Func<T, TResult> func)
        //{
        //    return end =>
        //    {
        //        TResult result = default(TResult);

        //        return result;
        //    };
        //}

        #endregion

        #region Where

        public static Func<dynamic> Where<TResult>(this Func<TResult> func, Func<TResult, bool> predicate)
        {
            return () =>
            {
                TResult result = func();
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }
        
        public static Func<T, dynamic> Where<T, TResult>(this Func<T, TResult> func, Func<TResult, bool> predicate)
        {
            return a =>
            {
                TResult result = func(a);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, dynamic> Where<T1, T2, TResult>(this Func<T1, T2, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b) =>
            {
                TResult result = func(a, b);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, dynamic> Where<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c) =>
            {
                TResult result = func(a, b, c);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, T4, dynamic> Where<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c, d) =>
            {
                TResult result = func(a, b, c, d);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, T4, T5, dynamic> Where<T1, T2, T3, T4, T5, TResult>(this Func<T1, T2, T3, T4, T5, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c, d, e) =>
            {
                TResult result = func(a, b, c, d, e);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, T4, T5, T6, dynamic> Where<T1, T2, T3, T4, T5, T6, TResult>(this Func<T1, T2, T3, T4, T5, T6, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c, d, e, f) =>
            {
                TResult result = func(a, b, c, d, e, f);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, T4, T5, T6, T7, dynamic> Where<T1, T2, T3, T4, T5, T6, T7, TResult>(this Func<T1, T2, T3, T4, T5, T6, T7, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c, d, e, f, g) =>
            {
                TResult result = func(a, b, c, d, e, f, g);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        public static Func<T1, T2, T3, T4, T5, T6, T7, T8, dynamic> Where<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(this Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> func, Func<TResult, bool> predicate)
        {
            return (a, b, c, d, e, f, g, h) =>
            {
                TResult result = func(a, b, c, d, e, f, g, h);
                if (predicate(result))
                {
                    return result;
                }
                else
                {
                    return null;
                }
            };
        }

        #endregion
    }
}
